using UnityEditor;
using UnityEngine;

using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;


namespace Hont.C2UTool
{
    public static class C2UAnalyzer
    {
        static C2UStructs mContext;


        public static void BuildFromStruct(C2UStructs context)
        {
            var oriTime = EditorApplication.timeSinceStartup;

            mContext = context;

            CreateLinkInfoFile(mContext);
            ApplyFBXModelImporterModify(mContext);

            mContext.ContextInfo.AnimationClip = SplitAnimationClip(mContext);

            ReplacePathPrefix(context);

            ApplyAnimationClipOperation(mContext);
            ApplyAnimationCurve(mContext);
            ApplyAttachAnimation(mContext);
            ApplyMaterialInfo(mContext);
            ApplyStaticAttachInfo(mContext);

            ApplyOutputAvatarInfo(mContext);

            SavePrefab(mContext);

            AssetDatabase.SaveAssets();

            C2UUtility.ApplyChangedAnimationClip();
            ApplyAttachOutputPath(mContext);

            AssetDatabase.Refresh();

            //var timeSub = new TimeSpan(0, 0, Convert.ToInt32((EditorApplication.timeSinceStartup - oriTime)));
            EditorUtility.DisplayDialog("Tip", string.Format("Analyze Completed!"), "OK");
        }

        public static C2UStructs InitContext(string attachInfo)
        {
            var result = new C2UStructs();

            #region ---Sub Component Initiation---
            result.SubComponentInfo = new C2UStructs.SubComponentStruct();

            var animationInfoAnalyzersArr = C2UUtility
                .GetAssemblyInterfacesAndCreate<IC2UAnimationInfoAnalyzer>(System.Reflection.Assembly.Load("Assembly-CSharp-Editor"));

            Array.Sort(animationInfoAnalyzersArr, (x, y) => -x.Priority.CompareTo(y.Priority));

            var staticInfoAnalyzersArr = C2UUtility
                .GetAssemblyInterfacesAndCreate<IC2UStaticInfoAnalyzer>(System.Reflection.Assembly.Load("Assembly-CSharp-Editor"));

            Array.Sort(staticInfoAnalyzersArr, (x, y) => -x.Priority.CompareTo(y.Priority));

            result.SubComponentInfo.AnimationInfoAnalyzersArr = animationInfoAnalyzersArr;
            result.SubComponentInfo.StaticInfoAnalyzersArr = staticInfoAnalyzersArr;
            #endregion

            #region ---Analyze StringInitiation---
            var lv1Categorys = attachInfo.Split('|');
            //The outest category,through '|' symbol split the contents.
            //Index 0 - Base Info(fbx path,is loop animation,etc)
            //Index 1 - Attach Animation Info(script animation, active animation etc)
            //Index 2 - Animation Event Info
            //Index 3 - Animation Curve Info
            //Index 4 - Static Info(component replace, custom script,camera etc)
            //Index 5 - Material Info(material and shader)

            result.BaseInfo = AnalyzeBaseInfo(lv1Categorys[0]);
            result.AnimationInfoArr = AnalyzeAnimationInfo(result, lv1Categorys[1]);
            result.AnimationEventInfoArr = AnalyzeAnimationEventInfo(lv1Categorys[2]);
            result.AnimationCurveInfoArr = AnalyzeAnimationCurveInfo(lv1Categorys[3]);
            result.StaticInfoArr = AnalyzeStaticInfo(result, lv1Categorys[4]);
            result.MaterialInfoArr = AnalyzeMaterialInfo(lv1Categorys[5]);
            #endregion

            #region ---Context Initiation---
            result.ContextInfo = new C2UStructs.ContextStruct();
            result.BaseInfo.FBXPath = result.BaseInfo.FBXPath.Replace("\\", "/");
            result.ContextInfo.FBXAssetsPath = C2UUtility.LastSubstring(result.BaseInfo.FBXPath, "Assets/", true);

            var sourceTemplate = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(result.ContextInfo.FBXAssetsPath);

            if (sourceTemplate == null)
            {
                throw new NullReferenceException("Not found import file path: " + result.BaseInfo.FBXPath);
            }
            var instancedGO = UnityEngine.Object.Instantiate(sourceTemplate) as GameObject;
            var cloneLen = "(Clone)".Length;
            instancedGO.name = instancedGO.name.Remove(instancedGO.name.Length - cloneLen, cloneLen);

            result.ContextInfo.InstancedGO = instancedGO;
            result.ContextInfo.DirectoryPath = C2UUtility.GetDirectory(result.BaseInfo.FBXPath);
            result.ContextInfo.FBXAssetsDirectoryPath = C2UUtility.GetDirectory(result.ContextInfo.FBXAssetsPath);
            result.ContextInfo.FBXName = result.BaseInfo.FBXPath.Contains("@")
                ? C2UUtility.RemoveExt(C2UUtility.LastSubstring(result.BaseInfo.FBXPath, "@"))
                : C2UUtility.RemoveExt(C2UUtility.LastSubstring(result.BaseInfo.FBXPath, "/"));
            result.ContextInfo.ModelImporter = AssetImporter.GetAtPath(result.ContextInfo.FBXAssetsPath) as ModelImporter;

            C2UUtility.ClearChangedAnimationClipCache();
            #endregion

            return result;
        }

        public static void DisposeContext(C2UStructs context)
        {
            UnityEngine.Object.DestroyImmediate(context.ContextInfo.InstancedGO);
        }

        /// <summary>
        /// Analyze base info(fbx path,is loop animation,etc).
        /// </summary>
        static C2UStructs.BaseInfoStruct AnalyzeBaseInfo(string baseInfo)
        {
            var result = new C2UStructs.BaseInfoStruct();
            var baseInfoCategorys = C2UUtility.SplitEx(baseInfo, '#', '-');
            result.FBXPath = C2UUtility.SecurityStringDecode(baseInfoCategorys[0][1]);
            result.C4DFilePath = C2UUtility.SecurityStringDecode(baseInfoCategorys[1][1]).Replace("\\", "/");
            result.PrefabAttachOutputPath = C2UUtility.SecurityStringDecode(baseInfoCategorys[2][1]);
            result.AnimationAttachOutputPath = C2UUtility.SecurityStringDecode(baseInfoCategorys[3][1]);
            result.IsLegacyAnimation = bool.Parse(baseInfoCategorys[4][1]);
            result.IsLoopAnimation = bool.Parse(baseInfoCategorys[5][1]);
            result.IsEnableRootMotion = bool.Parse(baseInfoCategorys[6][1]);
            result.RootBoneName = baseInfoCategorys[7][1];

            result.IsKeyframedAllObjects = bool.Parse(baseInfoCategorys[8][1]);
            result.AnimationCompressionType = (ModelImporterAnimationCompression)Enum.Parse(typeof(ModelImporterAnimationCompression), baseInfoCategorys[9][1]);
            result.PositionCompressionError = float.Parse(baseInfoCategorys[10][1]);
            result.ScaleCompressionError = float.Parse(baseInfoCategorys[11][1]);
            result.RotationCompressionError = float.Parse(baseInfoCategorys[12][1]);
            result.BuildAvatarInfo = bool.Parse(baseInfoCategorys[13][1]);

            result.FrameRate = float.Parse(baseInfoCategorys[14][1]);
            result.DefaultShader = C2UUtility.SecurityStringDecode(baseInfoCategorys[15][1]);

            return result;
        }

        /// <summary>
        /// FBX attach curve info(For instance 'Gravity' curve).
        /// </summary>
        static C2UStructs.AnimationCurveInfoStruct[] AnalyzeAnimationCurveInfo(string animationCurveInfo)
        {
            if (string.IsNullOrEmpty(animationCurveInfo)) return new C2UStructs.AnimationCurveInfoStruct[0];

            C2UStructs.AnimationCurveInfoStruct[] result = null;
            var curvesStrArr = animationCurveInfo.Split(',');
            result = new C2UStructs.AnimationCurveInfoStruct[curvesStrArr.Length];

            for (int i = 0; i < result.Length; i++)
            {
                var curveInfo = new C2UStructs.AnimationCurveInfoStruct();
                var curveFieldsStrArr = C2UUtility.SplitEx(curvesStrArr[i], '#', '-');

                curveInfo.Name = curveFieldsStrArr[0][1];
                curveInfo.Curve = AnalyzeCurve(curveFieldsStrArr[1][1]);

                result[i] = curveInfo;
            }

            return result;
        }

        static AnimationCurve AnalyzeCurve(string curveStr, char memberSplitKey = '&', char fieldSplitKey = '@', char valueSplitKey = ':')
        {
            var result = new AnimationCurve();

            var convCurveStr = C2UUtility.SecurityStringDecode(curveStr);
            var curvesStrArr = convCurveStr.Split(memberSplitKey);

            for (int i = 0; i < curvesStrArr.Length; i++)
            {
                var fieldsStrArr = C2UUtility.SplitEx(curvesStrArr[i], fieldSplitKey, valueSplitKey);

                var keyframe = new Keyframe();
                keyframe.time = float.Parse(fieldsStrArr[0][1]);
                keyframe.value = float.Parse(fieldsStrArr[1][1]);
                result.AddKey(keyframe);
            }

            return result;
        }

        /// <summary>
        /// Analyze attach animation info(script animation, active animation etc).
        /// </summary>
        static C2UStructs.AnimationInfoStruct[] AnalyzeAnimationInfo(C2UStructs context, string animationInfo)
        {
            if (string.IsNullOrEmpty(animationInfo)) return new C2UStructs.AnimationInfoStruct[0];
            C2UStructs.AnimationInfoStruct[] result = null;

            var analyzersCollection = context.SubComponentInfo.AnimationInfoAnalyzersArr;

            var animationCategorys = animationInfo.Split(',');
            result = new C2UStructs.AnimationInfoStruct[animationCategorys.Length];
            for (int i = 0; i < animationCategorys.Length; i++)
            {
                var animationFields = C2UUtility.SplitEx(animationCategorys[i], '#', '-');
                result[i] = new C2UStructs.AnimationInfoStruct();

                var targetAnalyzer = analyzersCollection.FirstOrDefault(m => m.CanSlover(result[i].Type, result[i].Property));

                var gameObjectPath = animationFields[0][1];
                var property = animationFields[1][1];
                var type = animationFields[2][1];

                result[i] = targetAnalyzer.AnalyzeExecute(gameObjectPath, type, property);
                result[i].Keyframes = AnalyzeKeyframeInfo(animationFields[3][1]);
            }

            return result;
        }

        /// <summary>
        /// Analyze animation event info.
        /// </summary>
        static C2UStructs.AnimationEventInfoStruct[] AnalyzeAnimationEventInfo(string animationEventInfo)
        {
            if (string.IsNullOrEmpty(animationEventInfo)) return new C2UStructs.AnimationEventInfoStruct[0];

            var animationEvents = animationEventInfo.Split(',');
            var result = new C2UStructs.AnimationEventInfoStruct[animationEvents.Length];

            for (int i = 0; i < animationEvents.Length; i++)
            {
                var item = animationEvents[i];
                var infoArr = C2UUtility.SplitEx(item, '#', '-');

                result[i] = new C2UStructs.AnimationEventInfoStruct();
                result[i].Time = float.Parse(infoArr[0][1]);
                result[i].MethodName = infoArr[1][1];
                result[i].Argument = int.Parse(infoArr[2][1]);
            }

            return result;
        }

        static C2UStructs.KeyframeInfoStruct[] AnalyzeKeyframeInfo(string keyframeInfo)
        {
            C2UStructs.KeyframeInfoStruct[] result = null;
            var keyframes = keyframeInfo.Split('&');

            result = new C2UStructs.KeyframeInfoStruct[keyframes.Length];

            for (int i = 0; i < result.Length; i++)
            {
                var keyframeFields = C2UUtility.SplitEx(keyframes[i], '@', ':');

                var item = new C2UStructs.KeyframeInfoStruct();
                item.Time = float.Parse(keyframeFields[0][1]);
                item.Value = float.Parse(C2UUtility.SecurityStringDecode(keyframeFields[1][1]));
                result[i] = item;
            }
            return result;
        }

        /// <summary>
        /// Analyze static info(component replace, custom script,camera etc)
        /// </summary>
        static C2UStructs.StaticInfoStruct[] AnalyzeStaticInfo(C2UStructs context, string staticInfo)
        {
            if (string.IsNullOrEmpty(staticInfo)) return new C2UStructs.StaticInfoStruct[0];

            var staticCategorys = staticInfo.Split(',');
            var result = new C2UStructs.StaticInfoStruct[staticCategorys.Length];
            var analyzersCollection = context.SubComponentInfo.StaticInfoAnalyzersArr;

            for (int i = 0; i < staticCategorys.Length; i++)
            {
                var replaceFields = C2UUtility.SplitEx(staticCategorys[i], '#', '-');
                result[i] = new C2UStructs.StaticInfoStruct();
                result[i].GameObjectPath = replaceFields[0][1];
                result[i].ComponentName = replaceFields[1][1];

                var argInfo = analyzersCollection
                    .FirstOrDefault(m => m.CanSlover(result[i].ComponentName))
                    .Analyze(result[i].GameObjectPath, result[i].ComponentName, replaceFields[2][1]);

                result[i].Values = argInfo;
            }

            return result;
        }

        /// <summary>
        /// Analyze material info(material and shader)
        /// </summary>
        static C2UStructs.MaterialInfoStruct[] AnalyzeMaterialInfo(string materialInfo)
        {
            if (string.IsNullOrEmpty(materialInfo)) return new C2UStructs.MaterialInfoStruct[0];

            var materialCategorys = materialInfo.Split(',');
            var result = new C2UStructs.MaterialInfoStruct[materialCategorys.Length];

            for (int i = 0; i < materialCategorys.Length; i++)
            {
                var fields = C2UUtility.SplitEx(materialCategorys[i], '#', '-');
                //Custom Material Categorys.
                //Index 0-Material name
                //Index 1-Linked image name
                //Index 2-Linked this material objects.
                //Index 3-Material shader name.
                //Index 4-Material shader content.

                result[i] = new C2UStructs.MaterialInfoStruct();
                result[i].MaterialName = C2UUtility.SecurityStringDecode(fields[0][1]);
                result[i].LinkedImageName = C2UUtility.SecurityStringDecode(fields[1][1]);
                if (fields[2][1].Contains('@'))//linked obj.
                {
                    result[i].UsedMatObjectArray = fields[2][1].Split('@').ToArray();
                }
                else
                {
                    result[i].UsedMatObjectArray = new string[] { fields[2][1] };
                }
                result[i].UsedMatObjectArray = C2UUtility.ConvertAll(result[i].UsedMatObjectArray, m => C2UUtility.SecurityStringDecode(m))
                    .ToArray();

                if (!string.IsNullOrEmpty(fields[3][1]))
                {
                    result[i].ShaderInfo = AnalyzeShaderInfo(shaderName: fields[3][1], shaderFieldInfo: fields[4][1]);
                }
            }

            return result;
        }

        static C2UStructs.ShaderInfoStruct AnalyzeShaderInfo(string shaderName, string shaderFieldInfo)
        {
            var result = new C2UStructs.ShaderInfoStruct();
            var fieldsStrArr = shaderFieldInfo.Split('&');
            result.ShaderName = C2UUtility.SecurityStringDecode(shaderName);
            result.Values = new C2UStructs.ShaderFieldStruct[fieldsStrArr.Length];

            for (int i = 0; i < result.Values.Length; i++)
            {
                var itemStrInfoArr = C2UUtility.SplitEx(fieldsStrArr[i], '@', ':');
                var item = new C2UStructs.ShaderFieldStruct();

                item.Key = C2UUtility.SecurityStringDecode(itemStrInfoArr[0][1]);
                item.Type = itemStrInfoArr[1][1];
                item.Value = C2UUtility.SecurityStringDecode(itemStrInfoArr[2][1]);
                result.Values[i] = item;
            }

            return result;
        }

        /// <summary>
        /// Link to cinema4d and open info file.
        /// </summary>
        static void CreateLinkInfoFile(C2UStructs context)
        {
            try
            {
                var dir = context.ContextInfo.DirectoryPath;
                var path = C2UUtility.CombinePath(dir, "LinkInfo.txt");

                var content = context.BaseInfo.C4DFilePath;
                content = C2UUtility.ToRelativePath(dir, content, '/');

                if (File.Exists(path)) File.Delete(path);
                File.AppendAllText(path, content);
            }
            catch (Exception e)
            {
                Debug.LogError("Create Link Info File Fail " + e);
            }
        }

        /// <summary>
        /// Removed c2u_ prefix string(hierarchy and animation clip).
        /// </summary>
        static void ReplacePathPrefix(C2UStructs context)
        {
            Func<string, string> replaceC2UPrefix = (str) =>
            {
                var replacedStr = "";
                var slashInx = -1;
                var keywordInx = -1;
                var mainInx = 0;

                do
                {
                    keywordInx = str.IndexOf("_C2U_", mainInx);

                    if (keywordInx == -1)
                    {
                        replacedStr = str;
                        break;
                    }

                    slashInx = str.IndexOf('/', mainInx);

                    if (slashInx == -1)
                    {
                        var start = keywordInx + 5;
                        var end = str.Length - start;
                        replacedStr += str.Substring(start, end);
                        mainInx = str.Length;
                    }
                    else
                    {
                        slashInx += 1;
                        var start = keywordInx + 5;
                        var end = slashInx - start;
                        replacedStr += str.Substring(start, end);
                        mainInx = slashInx;
                    }

                } while (mainInx < str.Length);

                return replacedStr;
            };//Fastest

            var hierObjList = new List<Transform>();
            C2UUtility.ChildForEach(context.ContextInfo.InstancedGO.transform, m => hierObjList.Add(m));

            for (int i = 0; i < hierObjList.Count; i++)
            {
                var item = hierObjList[i];
                if (!item.name.Contains("_C2U_")) continue;

                item.name = C2UUtility.LastSubstring(item.name, "_C2U_");
            }

            if (context.ContextInfo.AnimationClip != null)
            {
                C2UUtility.ChangeAnimationClipCurvePath(context.ContextInfo.AnimationClip, replaceC2UPrefix);
            }
        }

        /// <summary>
        /// Apply non-legacy animation curve,simular 'Gravity' etc.
        /// </summary>
        static void ApplyAnimationCurve(C2UStructs context)
        {
            var modelImporter = AssetImporter.GetAtPath(context.ContextInfo.FBXAssetsPath) as ModelImporter;
            if (modelImporter.defaultClipAnimations == null || modelImporter.defaultClipAnimations.Length == 0) return;

            var newClipAnimation = modelImporter.defaultClipAnimations[0];

            var clipAnimArr = new ClipAnimationInfoCurve[context.AnimationCurveInfoArr.Length];
            for (int i = 0; i < clipAnimArr.Length; i++)
            {
                var item = clipAnimArr[i];

                item.name = context.AnimationCurveInfoArr[i].Name;
                item.curve = context.AnimationCurveInfoArr[i].Curve;

                clipAnimArr[i] = item;
            }

            newClipAnimation.curves = clipAnimArr;

            modelImporter.clipAnimations = new ModelImporterClipAnimation[1] { newClipAnimation };
            modelImporter.SaveAndReimport();
        }

        /// <summary>
        /// Attach to animation clip animations.
        /// </summary>
        static void ApplyAttachAnimation(C2UStructs context)
        {
            var clip = context.ContextInfo.AnimationClip;
            if (clip == null) return;

            var analyzersCollection = context.SubComponentInfo.AnimationInfoAnalyzersArr;

            for (int i = 0; i < context.AnimationInfoArr.Length; i++)
            {
                var item = context.AnimationInfoArr[i];

                var curve = new AnimationCurve();
                var keys = new Keyframe[item.Keyframes.Length];
                for (int j = 0; j < item.Keyframes.Length; j++)
                {
                    var keyframe = item.Keyframes[j];
                    keys[j] = new Keyframe();
                    keys[j].time = keyframe.Time;
                    keys[j].value = Convert.ToSingle(keyframe.Value);
                }
                curve.keys = keys;
                var targetAnalyzer = analyzersCollection.FirstOrDefault(m => m.CanSlover(item.Type, item.Property));
                targetAnalyzer.ApplyToTarget(item, clip, curve);
            }

            #region ---Apply Animation Event---
            var newAnimEventArr = new AnimationEvent[context.AnimationEventInfoArr.Length];
            for (int i = 0; i < context.AnimationEventInfoArr.Length; i++)
            {
                var item = context.AnimationEventInfoArr[i];

                newAnimEventArr[i] = new AnimationEvent() { time = item.Time, functionName = item.MethodName, intParameter = item.Argument };
            }
            AnimationUtility.SetAnimationEvents(clip, newAnimEventArr);
            #endregion
        }

        static void ApplyStaticAttachInfo(C2UStructs context)
        {
            var instancedGO = context.ContextInfo.InstancedGO;
            var clip = context.ContextInfo.AnimationClip;

            var c4dEditorCameraFile = C2UUtility
                .ChildToEnumerableT(instancedGO.transform, false)
                .FirstOrDefault(m => m.name == "CINEMA_4D_Editor");

            if (c4dEditorCameraFile != null)
                UnityEngine.Object.DestroyImmediate(c4dEditorCameraFile.gameObject);

            var animator = C2UUtility.FindAllChild<Animator>(instancedGO.transform);

            if (clip != null && animator != null && context.BaseInfo.IsLegacyAnimation)
            {
                var tmpGameObject = animator.gameObject;
                UnityEngine.Object.DestroyImmediate(animator);
                var animationComponent = tmpGameObject.AddComponent<Animation>();
                animationComponent.AddClip(clip, clip.name);
                animationComponent.clip = clip;
            }
            else if (clip != null && animator != null)
            {
                animator.applyRootMotion = context.BaseInfo.IsEnableRootMotion;
            }
            else
            {
                UnityEngine.Object.DestroyImmediate(animator);
            }

            var analyzersCollection = context.SubComponentInfo.StaticInfoAnalyzersArr;

            for (int i = 0; i < context.StaticInfoArr.Length; i++)
            {
                var item = context.StaticInfoArr[i];

                var target = instancedGO.transform.Find(item.GameObjectPath);

                if (target == null)
                {
                    throw new Exception(string.Format("Path not found:{0} static attach info(component name):{1}", item.GameObjectPath, item.ComponentName));
                }

                analyzersCollection
                    .FirstOrDefault(m => m.CanSlover(item.ComponentName))
                    .ApplyToTarget(item, target.gameObject);
            }

            var staticInfoReceiverArr = instancedGO.transform
                .GetComponentsInChildren<Component>(true)
                .OfType<IC2UStaticInfoReceiver>()
                .ToArray();

            for (int i = 0; i < staticInfoReceiverArr.Length; i++)
                staticInfoReceiverArr[i].OnStaticInfoReplaced(instancedGO);

            for (int i = 0; i < staticInfoReceiverArr.Length; i++)
                staticInfoReceiverArr[i].OnStaticInfoReplacedLate(instancedGO);
        }

        static void ApplyOutputAvatarInfo(C2UStructs context)
        {
            if (!context.BaseInfo.BuildAvatarInfo) return;

            var dirPath = context.ContextInfo.FBXAssetsDirectoryPath;

            var avatarMask = new AvatarMask();

            var avatarPath = C2UUtility.CombinePath(dirPath, context.ContextInfo.FBXName + "_BuildedAvatar.asset");
            var avatarMaskPath = C2UUtility.CombinePath(dirPath, context.ContextInfo.FBXName + "_BuildedMask.mask");

            if (File.Exists(avatarPath)) File.Delete(avatarPath);
            if (File.Exists(avatarMaskPath)) File.Delete(avatarMaskPath);

            //--------------------
            var buildAvatar = AvatarBuilder.BuildGenericAvatar(mContext.ContextInfo.InstancedGO, mContext.BaseInfo.IsEnableRootMotion ? mContext.BaseInfo.RootBoneName : "");
            //-------------------- Avatar Process.

            //--------------------
            var pathArray = C2UUtility.ChildToEnumerableT(context.ContextInfo.InstancedGO.transform, true)
                .Select(m => C2UUtility.GetFullTransformPath(m, mContext.ContextInfo.InstancedGO.transform))
                .ToArray();

            var avatarMaskUtility = Type.GetType("UnityEditor.AvatarMaskUtility, UnityEditor");
            var updateTransformMaskMethod = avatarMaskUtility.GetMethod("UpdateTransformMask", new Type[] { typeof(AvatarMask), typeof(string[]), typeof(string[]) });
            updateTransformMaskMethod.Invoke(null, new object[] { avatarMask, pathArray, null });
            //-------------------- Avatar Mask Process.

            AssetDatabase.CreateAsset(avatarMask, avatarMaskPath);
            AssetDatabase.CreateAsset(buildAvatar, avatarPath);
        }

        static void ApplyShaderInfo(string dir, C2UStructs.MaterialInfoStruct matInfo, Material mat)
        {
            if (matInfo.ShaderInfo == null) return;

            var newShader = Shader.Find(matInfo.ShaderInfo.ShaderName);

            if (newShader == null) return;

            mat.shader = newShader;

            for (int i = 0; i < matInfo.ShaderInfo.Values.Length; i++)
            {
                var item = matInfo.ShaderInfo.Values[i];

                switch (item.Type)
                {
                    case "float":
                        mat.SetFloat(item.Key, float.Parse(item.Value.ToString()));
                        break;

                    case "color":
                        var colorStrArr = item.Value.ToString().Split('_');
                        mat.SetColor(item.Key
                            , new Color(
                                float.Parse(colorStrArr[0])
                                , float.Parse(colorStrArr[1])
                                , float.Parse(colorStrArr[2])
                                , float.Parse(colorStrArr[3])));
                        break;

                    case "vector":
                        var vectorStrArr = item.Value.ToString().Split('_');
                        mat.SetVector(item.Key
                            , new Vector4(
                                float.Parse(vectorStrArr[0])
                                , float.Parse(vectorStrArr[1])
                                , float.Parse(vectorStrArr[2])
                                , float.Parse(vectorStrArr[3])));
                        break;

                    case "keyword":
                        mat.EnableKeyword(item.Key);
                        break;

                    case "texture":
                        var temp = item.Value.ToString().Split('_');
                        var texPath = C2UUtility.SecurityStringDecode(temp[0]);
                        var offsetX = float.Parse(temp[1]);
                        var offsetY = float.Parse(temp[2]);
                        var scaleX = float.Parse(temp[3]);
                        var scaleY = float.Parse(temp[4]);

                        if (!string.IsNullOrEmpty(texPath))
                        {
                            var finalPath = C2UUtility.CombinePath(dir, texPath);
                            var tex = AssetDatabase.LoadAssetAtPath<Texture>(finalPath);
                            if (tex == null)
                                Debug.LogError("Not Fount Texture, Please Check Path: " + finalPath);

                            mat.SetTexture(item.Key, tex);
                        }

                        mat.SetTextureOffset(item.Key, new Vector2(offsetX, offsetY));
                        mat.SetTextureScale(item.Key, new Vector2(scaleX, scaleY));

                        break;

                    case "cubemap":
                        var cubemapInfo = item.Value.ToString().Split('_');
                        if (cubemapInfo.Length == 0) break;

                        var texs = new Texture2D[6];

                        for (int j = 0; j < texs.Length; j++)
                        {
                            if (string.IsNullOrEmpty(cubemapInfo[j])) continue;

                            var finalPath = C2UUtility.CombinePath(dir, C2UUtility.SecurityStringDecode(cubemapInfo[j]));
                            texs[j] = AssetDatabase.LoadAssetAtPath<Texture2D>(finalPath);
                            if (texs[j] == null)
                                Debug.LogError("Not Fount Texture, Please Check Path: " + finalPath);
                        }

                        var cubemap = C2UUtility.BuildCubemap(texs);
                        var cubemapPath = C2UUtility.CombinePath(dir, C2UUtility.CreateGUID() + ".cubemap");
                        AssetDatabase.CreateAsset(cubemap, cubemapPath);
                        var createdCubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(cubemapPath);

                        mat.SetTexture(item.Key, createdCubemap);

                        break;
                }
            }
        }

        static void ApplyMaterialInfo(C2UStructs context)
        {
            var dirPath = context.ContextInfo.FBXAssetsDirectoryPath;
            var instancedGO = context.ContextInfo.InstancedGO;

            var defaultMatCacheDict = new Dictionary<string, bool>();

            for (int i = 0; i < context.MaterialInfoArr.Length; i++)
            {
                var item = context.MaterialInfoArr[i];
                var linkedTexture = AssetDatabase.LoadAssetAtPath<Texture>(dirPath + "/" + item.LinkedImageName);

                var matPath = string.Format("{0}/{1}.mat", dirPath, item.MaterialName);
                if (!File.Exists(matPath))
                {
                    var defaultShader = string.IsNullOrEmpty(context.BaseInfo.DefaultShader)
                        ? "Standard"
                        : context.BaseInfo.DefaultShader;

                    var targetShader = Shader.Find(defaultShader);

                    if (targetShader == null)
                    {
                        throw new NullReferenceException("Not found default shader! " + defaultShader);
                    }

                    AssetDatabase.CreateAsset(new Material(targetShader), matPath);
                }

                var newMat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
                newMat.mainTexture = linkedTexture;

                ApplyShaderInfo(dirPath, item, newMat);

                for (int j = 0; j < item.UsedMatObjectArray.Length; j++)
                {
                    var usedMatObjectPath = item.UsedMatObjectArray[j];
                    var targetObject = instancedGO.transform.Find(usedMatObjectPath);
                    var renderer = targetObject.GetComponent<Renderer>();

                    if (renderer != null)
                    {
                        if (!defaultMatCacheDict.ContainsKey(usedMatObjectPath))//fast the list.
                        {
                            //replace default material, linked fbx material.
                            renderer.sharedMaterials = new Material[0];
                            defaultMatCacheDict.Add(usedMatObjectPath, true);
                        }

                        var tmpList = new List<Material>(renderer.sharedMaterials);
                        tmpList.Add(newMat);
                        renderer.sharedMaterials = tmpList.ToArray();
                    }
                }
            }

            AssetDatabase.SaveAssets();
        }

        /// <summary>
        /// Apply attach file copy output.
        /// </summary>
        static void ApplyAttachOutputPath(C2UStructs context)
        {
            var clip = context.ContextInfo.AnimationClip;

            //Paste animation clip to attach path.
            if (!string.IsNullOrEmpty(context.BaseInfo.AnimationAttachOutputPath) && clip != null)
            {
                var animAttachOutputPath = context.BaseInfo.AnimationAttachOutputPath;
                var newClipPath = C2UUtility.GetAssetsAbsolutePath(clip);

                if (File.Exists(animAttachOutputPath))
                    File.Delete(animAttachOutputPath);

                var animDir = C2UUtility.GetDirectory(animAttachOutputPath);
                if (!Directory.Exists(animDir))
                    Directory.CreateDirectory(animDir);

                File.Copy(newClipPath, animAttachOutputPath);
            }

            //Paste prefab to attach path.
            if (!string.IsNullOrEmpty(context.BaseInfo.PrefabAttachOutputPath))
            {
                var prefabAttachOutputPath = context.BaseInfo.PrefabAttachOutputPath;
                var prefabDir = context.ContextInfo.DirectoryPath;
                var fbxName = context.ContextInfo.FBXName;

                var prefabPath = C2UUtility.CombinePath(prefabDir, fbxName + ".prefab");

                if (File.Exists(prefabAttachOutputPath))
                    File.Delete(prefabAttachOutputPath);

                var attachPrefabDir = C2UUtility.GetDirectory(prefabAttachOutputPath);
                if (!Directory.Exists(attachPrefabDir))
                    Directory.CreateDirectory(attachPrefabDir);

                if (File.Exists(prefabPath))
                    File.Copy(prefabPath, prefabAttachOutputPath);
            }
        }

        static void SavePrefab(C2UStructs context)
        {
            var fbxName = context.ContextInfo.FBXName;
            var dirPath = context.ContextInfo.FBXAssetsDirectoryPath;
            var prefabPath = C2UUtility.CombinePath(dirPath, fbxName + ".prefab");
            if (File.Exists(prefabPath)) File.Delete(prefabPath);
            PrefabUtility.CreatePrefab(prefabPath, mContext.ContextInfo.InstancedGO);
        }

        static void ApplyFBXModelImporterModify(C2UStructs context)
        {
            try
            {
                var modelImporter = context.ContextInfo.ModelImporter;

                modelImporter.animationCompression = context.BaseInfo.AnimationCompressionType;

                //Set animation compression error.
                modelImporter.animationPositionError = context.BaseInfo.PositionCompressionError;
                modelImporter.animationScaleError = context.BaseInfo.ScaleCompressionError;
                modelImporter.animationRotationError = context.BaseInfo.RotationCompressionError;

                var serializedObj = new SerializedObject(modelImporter);
                var rootNodeProperty = serializedObj.FindProperty("m_HumanDescription.m_RootMotionBoneName");
                //Set enable root motion.
                rootNodeProperty.stringValue = context.BaseInfo.IsEnableRootMotion ? context.BaseInfo.RootBoneName : "";
                serializedObj.ApplyModifiedProperties();
                serializedObj.SetIsDifferentCacheDirty();

                modelImporter.SaveAndReimport();
            }
            catch (Exception e)
            {
                Debug.LogError("fbxPath: " + context.BaseInfo.FBXPath);
                throw e;
            }
        }

        /// <summary>
        /// Split animation clip from fbx file.
        /// </summary>
        static AnimationClip SplitAnimationClip(C2UStructs context)
        {
            var fbxPath = context.ContextInfo.FBXAssetsPath;
            var dirPath = context.ContextInfo.FBXAssetsDirectoryPath;

            var clipName = fbxPath.Contains("@")
                ? C2UUtility.RemoveExt(C2UUtility.LastSubstring(fbxPath, "@"))
                : null;

            if (string.IsNullOrEmpty(clipName))
                return null;

            var clips = AssetDatabase.LoadAllAssetsAtPath(fbxPath)
                .Where(m => m is AnimationClip)
                .Cast<AnimationClip>();

            var clip = clips.FirstOrDefault(m => m.name == clipName);

            if (clip == null)
            {
                return null;
            }

            var clipPath = C2UUtility.CombinePath(dirPath, clip.name + ".anim");

            if (File.Exists(clipPath))
                File.Delete(clipPath);

            //Split animation clip from fbx file.
            var instantiatedClip = UnityEngine.Object.Instantiate(clip);
            AssetDatabase.CreateAsset(instantiatedClip, clipPath);
            return AssetDatabase.LoadAssetAtPath<AnimationClip>(clipPath);
        }

        /// <summary>
        /// Apply animation clip operation and from fbx file splited.
        /// </summary>
        static void ApplyAnimationClipOperation(C2UStructs context)
        {
            var instancedGO = context.ContextInfo.InstancedGO;
            var animClip = context.ContextInfo.AnimationClip;

            if (animClip == null) return;

            //Set fps.
            animClip.frameRate = context.BaseInfo.FrameRate;

            //Set loop animation.
            if (context.BaseInfo.IsLegacyAnimation)
            {
                animClip.wrapMode = context.BaseInfo.IsLoopAnimation ? WrapMode.Loop : WrapMode.Default;
            }
            else if (context.BaseInfo.IsLoopAnimation)
            {
                var animClipSettings = AnimationUtility.GetAnimationClipSettings(animClip);
                animClipSettings.loopTime = true;
                AnimationUtility.SetAnimationClipSettings(animClip, animClipSettings);
            }

            //Set is legacy animation.
            animClip.legacy = context.BaseInfo.IsLegacyAnimation;

            if (!string.IsNullOrEmpty(context.BaseInfo.RootBoneName))
            {
                var rootBoneName = C2UUtility.LastSubstring(context.BaseInfo.RootBoneName, "_C2U_");

                var rootBoneObject = C2UUtility.FindAllChild(instancedGO.transform, rootBoneName);
                if (rootBoneObject != null)
                {
                    C2UUtility.ChangeAnimationClipCurve(animClip, arr =>
                    {
                        var rootBonePath = C2UUtility.GetFullTransformPath(rootBoneObject.transform, instancedGO.transform);
                        return arr.Where(m => m.CurveBinding.path != rootBonePath).ToArray();
                    });
                }
            }//Removed Root Bone Animation.(Fixed Double Transform Move Question)

            #region ---Keyframed All Objects---
            if (context.BaseInfo.IsKeyframedAllObjects)
            {
                var animClipLength = animClip.length;
                C2UUtility.ChangeAnimationClipCurve(animClip, arr =>
                {
                    var animInfoList = new List<C2UUtility.AnimationInfoPair>(arr);


                    var hierObjArr = C2UUtility.ChildToEnumerableT(instancedGO.transform).ToArray();

                    for (int i = 0; i < hierObjArr.Length; i++)
                    {
                        var item = hierObjArr[i];
                        var itemPath = C2UUtility.GetFullTransformPath(item.transform, instancedGO.transform);

                        var bindingPosX = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalPosition.x" };
                        var bindingPosY = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalPosition.y" };
                        var bindingPosZ = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalPosition.z" };

                        var bindingRotX = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalRotation.x" };
                        var bindingRotY = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalRotation.y" };
                        var bindingRotZ = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalRotation.z" };
                        var bindingRotW = new EditorCurveBinding() { type = typeof(Transform), path = itemPath, propertyName = "m_LocalRotation.w" };

                        var posBindings = new EditorCurveBinding[] { bindingPosX, bindingPosY, bindingPosZ };
                        for (int j = 0; j < 3; j++)
                        {
                            if (animInfoList.Any(m => m.CurveBinding == posBindings[j])) continue;

                            var curve = new AnimationCurve();
                            var keys = new Keyframe[2];
                            keys[0] = new Keyframe(0, item.transform.localPosition[j]);
                            keys[1] = new Keyframe(animClipLength, item.transform.localPosition[j]);
                            curve.keys = keys;

                            animInfoList.Add(new C2UUtility.AnimationInfoPair() { CurveBinding = posBindings[j], AnimationCurve = curve });
                        }

                        var rotBindings = new EditorCurveBinding[] { bindingRotX, bindingRotY, bindingRotZ, bindingRotW };
                        for (int j = 0; j < 4; j++)
                        {
                            if (animInfoList.Any(m => m.CurveBinding == rotBindings[j])) continue;

                            var curve = new AnimationCurve();
                            var keys = new Keyframe[2];
                            keys[0] = new Keyframe(0, item.transform.localRotation[j]);
                            keys[1] = new Keyframe(animClipLength, item.transform.localRotation[j]);
                            curve.keys = keys;

                            animInfoList.Add(new C2UUtility.AnimationInfoPair() { CurveBinding = rotBindings[j], AnimationCurve = curve });
                        }
                    }

                    return animInfoList.ToArray();
                });//Keyframed all objects.
            }
            #endregion
        }
    }
}
